/*
 * Copyright (c) 2005-2009 Atheme Development Group
 * Copyright (c) 2009 Elias Shtift. ishtift@gmail.com.
 *
 * Rights to this code are documented in doc/LICENSE.
 *
 * This file contains protocol support for ircd-hybrid-ru
 *
 * $Id: hybrid-ru.c,v b2157df28b76 2009/02/22 21:15:04 dwr $
 */

#include "atheme.h"
#include "uplink.h"
#include "pmodule.h"
#include "protocol/hybrid-ru.h"

DECLARE_MODULE_V1("protocol/hybrid-ru", true, _modinit, NULL, "$Id: hybrid-ru.c,v b2157df28b76 2009/02/22 21:15:04 dwr $", "DalNet(RU)");

/* *INDENT-OFF* */

ircd_t HybridRU = {
        "hybrid-ru-7.2.4",		/* IRCd name */
        "$$",                           /* TLD Prefix, used by Global. */
        true,                           /* Whether or not we use IRCNet/TS6 UID */
        false,                          /* Whether or not we use RCOMMAND */
        false,                          /* Whether or not we support channel owners. */
        false,                          /* Whether or not we support channel protection. */
        true,                           /* Whether or not we support halfops. */
	false,				/* Whether or not we use P10 */
	true,				/* Whether or not we use vHosts. */
	0,				/* Oper-only cmodes */
        0,                              /* Integer flag for owner channel flag. */
        0,                              /* Integer flag for protect channel flag. */
        CSTATUS_HALFOP,                 /* Integer flag for halfops. */
        "+",                            /* Mode we set for owner. */
        "+",                            /* Mode we set for protect. */
        "+h",                           /* Mode we set for halfops. */
	PROTOCOL_RATBOX,		/* Protocol type */
	0,                              /* Permanent cmodes */
	0,                              /* Oper-immune cmode */
	"beI",                          /* Ban-like cmodes */
	'e',                            /* Except mchar */
	'I',                            /* Invex mchar */
	IRCD_CIDR_BANS                  /* Flags */
};

struct cmode_ hybridru_mode_list[] = {
  { 'i', CMODE_INVITE   },
  { 'm', CMODE_MOD      },
  { 'n', CMODE_NOEXT    },
  { 'p', CMODE_PRIV     },
  { 's', CMODE_SEC      },
  { 't', CMODE_TOPIC    },
  { 'c', CMODE_NOCOLOR  },
  { 'R', CMODE_REGONLY  },
  { 'O', CMODE_OPERONLY },
  { 'C', CMODE_STRIP    },
  { 'S', CMODE_SSLONLY  },
  { 'N', CMODE_NNCHGANGE},
  { '\0', 0 }
};

struct extmode hybridru_ignore_mode_list[] = {
  { '\0', 0 }
};

struct cmode_ hybridru_status_mode_list[] = {
  { 'o', CSTATUS_OP    },
  { 'h', CSTATUS_HALFOP },
  { 'v', CSTATUS_VOICE },
  { '\0', 0 }
};

struct cmode_ hybridru_prefix_mode_list[] = {
  { '@', CSTATUS_OP    },
  { '%', CSTATUS_HALFOP },
  { '+', CSTATUS_VOICE },
  { '\0', 0 }
};

struct cmode_ hybridru_user_mode_list[] = {
  { 'a', UF_ADMIN    },
  { 'i', UF_INVIS    },
  { 'o', UF_IRCOP    },
  { '\0', 0 }
};

/* *INDENT-ON* */

static void m_tburst(sourceinfo_t *si, int parc, char *parv[])
{
       time_t chants = atol(parv[0]);
       channel_t *c = channel_find(parv[1]);
       time_t topicts = atol(parv[2]);

       if (c == NULL)
               return;

       /* Our uplink is trying to change the topic during burst,
        * and we have already set a topic. Assume our change won.
        * -- jilles */
       if (si->s != NULL && si->s->uplink == me.me &&
                       !(si->s->flags & SF_EOB) && c->topic != NULL)
               return;

       if (c->ts < chants || (c->ts == chants && c->topicts >= topicts))
       {
               slog(LG_DEBUG, "m_tburst(): ignoring topic on %s", c->name);
               return;
       }

       handle_topic_from(si, c, parv[3], topicts, parv[4]);
}

/* protocol-specific stuff to do on login
 * derived from hyperion protocol module
 * -- dwr
 */
static void hybridru_on_login(user_t *u, myuser_t *account, const char *wantedhost)
{
	metadata_t *md;

	if (!me.connected || u == NULL)
		return;

	/* set +R if they're identified to the nick they are using */
	if (should_reg_umode(u))
		sts(":%s SVSMODE %s +R", ME, u->nick);
}

/* protocol-specific stuff to do on login */
static bool hybridru_on_logout(user_t *u, const char *account)
{
	if (!me.connected || u == NULL)
		return false;

	if (!nicksvs.no_nick_ownership)
		sts(":%s SVSMODE %s -R", ME, u->nick);

	return false;
}

static void hybridru_fnc_sts(user_t *source, user_t *u, char *newnick, int type)
{
	sts(":%s SVSNICK %s %s", ME, u->nick, newnick);
}

static void hybridru_sethost_sts(user_t *source, user_t *target, const char *host)
{
	if (!me.connected)
		return;

	if (irccasecmp(target->host, host))
		numeric_sts(me.me, 396, target, "%s :is now your hidden host (set by %s)", host, source->nick);
	else
		numeric_sts(me.me, 396, target, "%s :hostname reset by %s", host, source->nick);

	sts(":%s SVSHOST %s :%s", ME, target->nick, host);
	sts(":%s SVSMODE %s +X", ME, target->nick, host);
}

static void nick_group(hook_user_req_t *hdata)
{
	user_t *u;

	u = hdata->si->su != NULL && !irccasecmp(hdata->si->su->nick, hdata->mn->nick) ? hdata->si->su : user_find_named(hdata->mn->nick);
	if (u != NULL && should_reg_umode(u))
		sts(":%s SVSMODE %s +R", ME, u->nick);
}

static void nick_ungroup(hook_user_req_t *hdata)
{
	user_t *u;

	u = hdata->si->su != NULL && !irccasecmp(hdata->si->su->nick, hdata->mn->nick) ? hdata->si->su : user_find_named(hdata->mn->nick);
	if (u != NULL && !nicksvs.no_nick_ownership)
		sts(":%s SVSMODE %s -R", ME, u->nick);
}

static void hybridru_kline_sts(char *server, char *user, char *host, long duration, char *reason)
{
	if (!me.connected)
		return;

	sts(":%s KLINE * %ld %s %s :%s", CLIENT_NAME(opersvs.me->me), duration, user, host, reason);
}

static void hybridru_unkline_sts(char *server, char *user, char *host)
{
	if (!me.connected)
		return;

	sts(":%s UNKLINE * %s %s", CLIENT_NAME(opersvs.me->me), user, host);
}

/* server-to-server QLINE wrapper */
static void ts6_qline_sts(char *server, char *name, long duration, char *reason)
{
	if (!me.connected)
		return;

//	sts(":%s RESV * %s :%s", CLIENT_NAME(opersvs.me->me), name, reason);
//	sts(":%s RESV * %ld %s 0 :%s", CLIENT_NAME(opersvs.me->me), duration, name, reason);
    sts(":%s ENCAP %s RESV %ld %s 0 :%s", CLIENT_NAME(opersvs.me->me), server, duration, name, reason);
}

/* server-to-server UNQLINE wrapper */
static void ts6_unqline_sts(char *server, char *name)
{
	if (!me.connected)
		return;

	sts(":%s UNRESV * %s", CLIENT_NAME(opersvs.me->me), name);
}

static void m_chghost(sourceinfo_t *si, int parc, char *parv[])
{
	user_t *u = user_find(parv[0]);

	if (!u)
		return;

	strlcpy(u->vhost, parv[1], HOSTLEN);
}

static void m_nick(sourceinfo_t *si, int parc, char *parv[])
{
	bool realchange;
	server_t *s;
	user_t *u;

	/* got the right number of args for an introduction? */
	if (parc == 8)
	{
		s = server_find(parv[6]);
		if (!s)
		{
			slog(LG_DEBUG, "m_nick(): new user on nonexistant server: %s", parv[6]);
			return;
		}

		slog(LG_DEBUG, "m_nick(): new user on `%s': %s", s->name, parv[0]);

		u = user_add(parv[0], parv[4], parv[5], NULL, NULL, NULL, parv[7], s, atoi(parv[2]));
		if (u == NULL)
			return;

		user_mode(u, parv[3]);

		/*
		if (strchr(parv[3], 'R'))
			handle_burstlogin(u, NULL, 0);
		*/

		/* If server is not yet EOB we will do this later.
		 * This avoids useless "please identify" -- jilles */
		if (s->flags & SF_EOB)
			handle_nickchange(user_find(parv[0]));
	}

	/* if it's only 2 then it's a nickname change */
	else if (parc == 2)
	{
                if (!si->su)
                {       
                        slog(LG_DEBUG, "m_nick(): server trying to change nick: %s", si->s != NULL ? si->s->name : "<none>");
                        return;
                }

		slog(LG_DEBUG, "m_nick(): nickname change from `%s': %s", si->su->nick, parv[0]);

		realchange = irccasecmp(si->su->nick, parv[0]);

		if (user_changenick(si->su, parv[0], atoi(parv[1])))
			return;

		/* fix up +r if necessary -- jilles */
		if (realchange && !nicksvs.no_nick_ownership)
		{
			if (should_reg_umode(si->su))
				/* changed nick to registered one, reset +r */
				sts(":%s SVSMODE %s +R", ME, parv[0]);
			else
				/* changed from registered nick, remove +r */
				sts(":%s SVSMODE %s -R", ME, parv[0]);
		}

		/* It could happen that our PING arrived late and the
		 * server didn't acknowledge EOB yet even though it is
		 * EOB; don't send double notices in that case -- jilles */
		if (si->su->server->flags & SF_EOB)
			handle_nickchange(si->su);
	}
	else
	{
		int i;
		slog(LG_DEBUG, "m_nick(): got NICK with wrong number of params");

		for (i = 0; i < parc; i++)
			slog(LG_DEBUG, "m_nick():   parv[%d] = %s", i, parv[i]);
	}
}

/* topic wrapper */
static void hybridru_topic_sts(channel_t *c, user_t *source, const char *setter, time_t ts, time_t prevts, const char *topic)
{
	int joined = 0;

	if (!me.connected || !c)
		return;

	/* If possible, try to use TB
	 * Note that because TOPIC does not contain topicTS, it may be
	 * off a few seconds on other servers, hence the 60 seconds here.
	 * -- jilles */
	if (*topic != '\0')
	{
		/* Restoring old topic || Tweaking a topic */
		if (ts <= prevts || prevts == 0)
		{
			ts=prevts + 1;
			sts(":%s TBURST %lu %s %lu %s :%s", ME, c->ts, c->name, (unsigned long)ts, setter, topic);
			c->topicts = ts;
			return;
		}
	}
	/* We have to be on channel to change topic.
	 * We cannot nicely change topic from the server:
	 * :server.name TOPIC doesn't propagate and TB requires
	 * us to specify an older topicts.
	 * -- jilles
	 */
	if (!chanuser_find(c, source))
	{
		sts(":%s SJOIN %lu %s + :@%s", ME, (unsigned long)c->ts, c->name, CLIENT_NAME(source));
		joined = 1;
	}
	sts(":%s TOPIC %s :%s", CLIENT_NAME(source), c->name, topic);
	if (joined)
		sts(":%s PART %s :Topic set for %s",
				CLIENT_NAME(source), c->name, setter);
	c->topicts = CURRTIME;
}

void _modinit(module_t * m)
{
	MODULE_TRY_REQUEST_DEPENDENCY(m, "protocol/ts6-generic");

	sethost_sts = &hybridru_sethost_sts;
	fnc_sts = &hybridru_fnc_sts;
	ircd_on_login = &hybridru_on_login;
	ircd_on_logout = &hybridru_on_logout;
	kline_sts = &hybridru_kline_sts;
	unkline_sts = &hybridru_unkline_sts;
	qline_sts = &ts6_qline_sts;
	unqline_sts = &ts6_unqline_sts;
	topic_sts = &hybridru_topic_sts;

	mode_list = hybridru_mode_list;
	ignore_mode_list = hybridru_ignore_mode_list;
	user_mode_list = hybridru_user_mode_list;
	status_mode_list = hybridru_status_mode_list;
	prefix_mode_list = hybridru_prefix_mode_list;

	ircd = &HybridRU;

	pcommand_add("SVSHOST", m_chghost, 2, MSRC_USER | MSRC_SERVER);
	pcommand_add("TBURST", m_tburst, 5, MSRC_SERVER);

	pcommand_delete("NICK");
	pcommand_add("NICK", m_nick, 2, MSRC_USER | MSRC_SERVER);

	hook_add_event("nick_group");
	hook_add_hook("nick_group", (void (*)(void *))nick_group);
	hook_add_event("nick_ungroup");
	hook_add_hook("nick_ungroup", (void (*)(void *))nick_ungroup);

	m->mflags = MODTYPE_CORE;

	pmodule_loaded = true;
}

/* vim:cinoptions=>s,e0,n0,f0,{0,}0,^0,=s,ps,t0,c3,+s,(2s,us,)20,*30,gs,hs
 * vim:ts=8
 * vim:sw=8
 * vim:noexpandtab
 */
